#include "Avalon/IO/Buffer.hpp"
#include <cstring>

using namespace Avalon;
using namespace Avalon::IO;
using namespace boost;
using namespace std;

namespace {
  int FindNextPowerOfTwo(int currentSize, int newSize) {
    if(currentSize == 0) {
      currentSize = 1;
    }
    while(currentSize < newSize) {
      currentSize <<= 1;
    }
    return currentSize;
  }
}

Buffer::Buffer()
    : m_size(0),
      m_availableSize(0) {}

Buffer::Buffer(int initialSize)
    : m_size(initialSize),
      m_availableSize(FindNextPowerOfTwo(1, initialSize)),
      m_data(new char[initialSize]) {}

Buffer::Buffer(const void* data, int size)
    : m_size(0),
      m_availableSize(0) {
  Append(data, size);
}

Buffer::Buffer(const Buffer& buffer)
    : m_size(buffer.m_size),
      m_availableSize(buffer.m_availableSize),
      m_data(buffer.m_data) {}

Buffer::~Buffer() {}

Buffer& Buffer::operator =(const Buffer& rhs) {
  m_size = rhs.m_size;
  m_availableSize = rhs.m_availableSize;
  m_data = rhs.m_data;
  return *this;
}

bool Buffer::IsEmpty() const {
  return m_size == 0;
}

void Buffer::Grow(int size) {
  if(size <= 0) {
    return;
  }
  m_size += size;
  if(m_availableSize >= m_size) {
    return;
  }
  m_availableSize = FindNextPowerOfTwo(m_availableSize, m_size);
  shared_array<char> oldData = m_data;
  m_data.reset(new char[m_availableSize]);
  memcpy(m_data.get(), oldData.get(), m_size - size);
}

void Buffer::Shrink(int size) {
  m_size -= size;
  assert(m_size >= 0);
}

void Buffer::Reserve(int size) {
  Grow(size - m_size);
}

void Buffer::Write(int index, const void* source, int size) {
  if(m_availableSize < index + size || !m_data.unique()) {
    m_availableSize = FindNextPowerOfTwo(m_availableSize, index + size);
    shared_array<char> oldData = m_data;
    m_data.reset(new char[m_availableSize]);
    memcpy(m_data.get(), oldData.get(), m_size);
  }
  memcpy(m_data.get() + index, source, size);
  m_size = max(index + size, m_size);
}

void Buffer::Append(const Buffer& buffer) {
  if(m_size == 0) {
    m_data = buffer.m_data;
    m_size = buffer.m_size;
    m_availableSize = buffer.m_availableSize;
    return;
  }
  Append(buffer.GetData(), buffer.GetSize());
}

void Buffer::Append(const void* data, int size) {
  if(size == 0) {
    return;
  }
  if(m_availableSize < m_size + size || !m_data.unique()) {
    m_availableSize = FindNextPowerOfTwo(m_availableSize, m_size + size);
    shared_array<char> oldData = m_data;
    m_data.reset(new char[m_availableSize]);
    memcpy(m_data.get(), oldData.get(), m_size);
  }
  memcpy(m_data.get() + m_size, data, size);
  m_size += size;
}

void Buffer::Swap(Buffer& buffer) {
  std::swap(m_data, buffer.m_data);
  std::swap(m_size, buffer.m_size);
  std::swap(m_availableSize, buffer.m_availableSize);
}

void Buffer::Reset() {
  m_size = 0;
}

const char* Buffer::GetData() const {
  return m_data.get();
}

char* Buffer::GetMutableData() {
  if(!m_data.unique()) {
    shared_array<char> oldData = m_data;
    m_data.reset(new char[m_availableSize]);
    memcpy(m_data.get(), oldData.get(), m_size);
  }
  return m_data.get();
}

int Buffer::GetSize() const {
  return m_size;
}

Buffer Avalon::IO::BufferFromString(const string& source) {
  return Buffer(source.c_str(), source.size());
}

void Avalon::IO::swap(Buffer& a, Buffer& b) {
  a.Swap(b);
}

template<> void std::swap(Buffer& a, Buffer& b) {
  a.Swap(b);
}
